package tc.de.krawallmann.slick.game;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.newdawn.slick.ControllerListener;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.MouseListener;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.transition.Transition;

import tc.de.krawallmann.achievement.Achievement;
import tc.de.krawallmann.extension.Extension;
import tc.de.krawallmann.extension.KeyboardExtension;
import tc.de.krawallmann.extension.PersistenceExtension;

/**
 * The Class AbstractExtendableGame.
 * @author KrawallMann
 * @version 1.0
 */
public abstract class AbstractExtendableGame extends AbstractGame {
	
	/** The extensions. */
	private transient final Map<String, Extension> extensions = new HashMap<String, Extension>();
	
	/** The initialized. */
	private transient boolean initialized;

	/**
	 * Instantiates a new abstract extendable game.
	 * 
	 * @param name
	 *            the name
	 */
	public AbstractExtendableGame(final String name) {
		super(name);
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

			@Override
			public void run() {
				storeAll();
			}

		}));
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerButtonPressed(int, int)
	 */
	@Override
	public void controllerButtonPressed(final int controller, final int button) {
		super.controllerButtonPressed(controller, button);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerButtonPressed(controller, button);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerButtonReleased(int, int)
	 */
	@Override
	public void controllerButtonReleased(final int controller, final int button) {
		super.controllerButtonReleased(controller, button);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerButtonReleased(controller, button);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerDownPressed(int)
	 */
	@Override
	public void controllerDownPressed(final int controller) {
		super.controllerDownPressed(controller);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerDownPressed(controller);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerDownReleased(int)
	 */
	@Override
	public void controllerDownReleased(final int controller) {
		super.controllerDownReleased(controller);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerDownReleased(controller);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerLeftPressed(int)
	 */
	@Override
	public void controllerLeftPressed(final int controller) {
		super.controllerLeftPressed(controller);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerLeftPressed(controller);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerLeftReleased(int)
	 */
	@Override
	public void controllerLeftReleased(final int controller) {
		super.controllerLeftReleased(controller);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerLeftReleased(controller);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerRightPressed(int)
	 */
	@Override
	public void controllerRightPressed(final int controller) {
		super.controllerRightPressed(controller);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerRightPressed(controller);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerRightReleased(int)
	 */
	@Override
	public void controllerRightReleased(final int controller) {
		super.controllerRightReleased(controller);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerRightReleased(controller);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerUpPressed(int)
	 */
	@Override
	public void controllerUpPressed(final int controller) {
		super.controllerUpPressed(controller);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerUpPressed(controller);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#controllerUpReleased(int)
	 */
	@Override
	public void controllerUpReleased(final int controller) {
		super.controllerUpReleased(controller);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof ControllerListener) {
				((ControllerListener) extension).controllerUpReleased(controller);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#enterState(int, org.newdawn.slick.state.transition.Transition, org.newdawn.slick.state.transition.Transition)
	 */
	@Override
	public void enterState(final int stateID, final Transition leave, final Transition enter) {
		if (!initialized) {
			registerExtensions();
			readAll();
			for (final Extension extension : extensions.values()) {
				extension.init();
			}
			initialized = true;
		}
		super.enterState(stateID, leave, enter);
	}

	/**
	 * Gets the extension.
	 * 
	 * @param <T>
	 *            the generic type
	 * @param clazz
	 *            the clazz
	 * @return the extension
	 */
	@SuppressWarnings("unchecked")
	public <T extends Extension> T getExtension(final Class<T> clazz) {
		if (extensions.containsKey(clazz.getName())) {
			final Extension ret = extensions.get(clazz.getName());
			if (clazz.equals(ret.getClass())) {
				return (T) ret;
			}
		}
		throw new IllegalArgumentException("No such Extension found: " + clazz.getName());
	}

	/**
	 * Gets the extensions.
	 * 
	 * @return the extensions
	 */
	public Collection<Extension> getExtensions() {
		return extensions.values();
	}

	/**
	 * Gets the possible achievements.
	 * 
	 * @return the possible achievements
	 */
	public abstract List<Achievement> getPossibleAchievements();

	/**
	 * Gets the user home.
	 * 
	 * @return the user home
	 */
	public final String getUserHome() {
		return System.getProperty("user.home");
	}

	/**
	 * Gets the version.
	 * 
	 * @return the version
	 */
	public abstract String getVersion();

	/* (non-Javadoc)
	 * @see tc.de.krawallmann.twl.AbstractTWLStateBasedGame#keyPressed(int, char)
	 */
	@Override
	public void keyPressed(final int key, final char code) {
		super.keyPressed(key, code);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof KeyboardExtension) {
				((KeyboardExtension) extension).keyPressed(key, code);
			}
		}
	}

	/* (non-Javadoc)
	 * @see tc.de.krawallmann.twl.AbstractTWLStateBasedGame#keyReleased(int, char)
	 */
	@Override
	public void keyReleased(final int key, final char code) {
		super.keyReleased(key, code);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof KeyboardExtension) {
				((KeyboardExtension) extension).keyReleased(key, code);
			}
		}
	}

	/* (non-Javadoc)
	 * @see tc.de.krawallmann.twl.AbstractTWLStateBasedGame#mouseClicked(int, int, int, int)
	 */
	@Override
	public void mouseClicked(final int button, final int xPos, final int yPos, final int clickCount) {
		super.mouseClicked(button, xPos, yPos, clickCount);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof MouseListener) {
				((MouseListener) extension).mouseClicked(button, xPos, yPos, clickCount);
			}
		}
	}

	/* (non-Javadoc)
	 * @see tc.de.krawallmann.twl.AbstractTWLStateBasedGame#mouseDragged(int, int, int, int)
	 */
	@Override
	public void mouseDragged(final int oldx, final int oldy, final int newx, final int newy) {
		super.mouseDragged(oldx, oldy, newx, newy);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof MouseListener) {
				((MouseListener) extension).mouseDragged(oldx, oldy, newx, newy);
			}
		}
	}

	/* (non-Javadoc)
	 * @see tc.de.krawallmann.twl.AbstractTWLStateBasedGame#mouseMoved(int, int, int, int)
	 */
	@Override
	public void mouseMoved(final int oldx, final int oldy, final int newx, final int newy) {
		super.mouseMoved(oldx, oldy, newx, newy);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof MouseListener) {
				((MouseListener) extension).mouseMoved(oldx, oldy, newx, newy);
			}
		}
	}

	/* (non-Javadoc)
	 * @see tc.de.krawallmann.twl.AbstractTWLStateBasedGame#mousePressed(int, int, int)
	 */
	@Override
	public void mousePressed(final int button, final int xPos, final int yPos) {
		super.mousePressed(button, xPos, yPos);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof MouseListener) {
				((MouseListener) extension).mousePressed(button, xPos, yPos);
			}
		}
	}

	/* (non-Javadoc)
	 * @see tc.de.krawallmann.twl.AbstractTWLStateBasedGame#mouseReleased(int, int, int)
	 */
	@Override
	public void mouseReleased(final int button, final int xPos, final int yPos) {
		super.mouseReleased(button, xPos, yPos);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof MouseListener) {
				((MouseListener) extension).mouseReleased(button, xPos, yPos);
			}
		}
	}

	/* (non-Javadoc)
	 * @see tc.de.krawallmann.twl.AbstractTWLStateBasedGame#mouseWheelMoved(int)
	 */
	@Override
	public void mouseWheelMoved(final int newValue) {
		super.mouseWheelMoved(newValue);
		for (final Extension extension : extensions.values()) {
			if (extension instanceof MouseListener) {
				((MouseListener) extension).mouseWheelMoved(newValue);
			}
		}
	}

	/**
	 * Read all.
	 * 
	 * @return true, if successful
	 */
	public boolean readAll() {
		boolean ret = true;
		for (final Extension extension : extensions.values()) {
			if (extension instanceof PersistenceExtension) {
				ret &= ((PersistenceExtension) extension).read();
			}
		}
		return ret;
	}

	/**
	 * Register extension.
	 * 
	 * @param extension
	 *            the extension
	 */
	public void registerExtension(final Extension extension) {
		extensions.put(extension.getClass().getName(), extension);
		if (initialized) {
			if (extension instanceof PersistenceExtension) {
				((PersistenceExtension) extension).read();
			}
			extension.init();
		}
	}

	/**
	 * Register extensions.
	 */
	public abstract void registerExtensions();

	/**
	 * Removes the extension.
	 * 
	 * @param clazz
	 *            the clazz
	 */
	public void removeExtension(final Class<Extension> clazz) {
		extensions.remove(clazz.getName());
	}

	/**
	 * Store all.
	 * 
	 * @return true, if successful
	 */
	public boolean storeAll() {
		boolean ret = true;
		for (final Extension extension : extensions.values()) {
			if (extension instanceof PersistenceExtension) {
				ret &= ((PersistenceExtension) extension).store();
			}
		}
		return ret;
	}

	/* (non-Javadoc)
	 * @see org.newdawn.slick.state.StateBasedGame#preUpdateState(org.newdawn.slick.GameContainer, int)
	 */
	@Override
	protected void preUpdateState(final GameContainer container, final int delta)
			throws SlickException {
		super.preUpdateState(container, delta);
		for (final Extension extension : extensions.values()) {
			extension.update(delta);
		}
	}
}
